Tutorial
The skeleton will include:
1. A csv import and save of column names
2. Creation of a dropdown menu for selecting the variable to be viewed
3. A "listener" to update the line plot when the variable is changed
4. Creation of variables that will govern menu dimensions and margins
5. Importantly, the attachment of the SVG to a div in the html file intended to hold the plot
6. The creation of graph objects for each axis, the line representing the data, and the graph title
7. A function to update the plot when a different variable is selected
8. A traditional listener that updates the plot when the window dimension is changed. (This is not technically necessary, but the plot is unimpressive when this is not included.)
Let us proceed one step at a time. Note that step 7. contains several parts.
1. A csv import and save of column names
// Global variables for D3 chart
let dataGlobal;
let svg;
let valueKey;
// Load CSV and initialize chart
d3.csv(filename, function(d) {
// Use computed property name so that the key remains x_var (e.g., "DATE")
const parsedDate = d3.timeParse("%Y-%m-%d")(d[x_var]);
let row = { [x_var]: parsedDate };
for (let key in d) {
if (key !== x_var) {
row[key] = +d[key];
}
}
return row;
}).then(function(data) {
dataGlobal = data;
const keys = Object.keys(data[0]).filter(k => k !== x_var);
if (keys.length === 0) {
console.error("No numeric variables found in CSV.");
return;
}
valueKey = keys[0];
2. Creation of a dropdown menu for selecting the variable to be viewed
// Populate the dropdown menu
d3.select("#variable-select")
.selectAll("option")
.data(keys)
.enter()
.append("option")
.attr("value", d => d)
.text(d => d);
3. A "listener" (from d3 module) to update the line plot when the variable is changed
d3.select("#variable-select")
.on("change", function() {
valueKey = this.value;
updateChart();
});
4. Creation of variables that will govern menu dimensions and margins
// Create the SVG container inside #linegraph
const margin = { top: 60, right: 30, bottom: 30, left: 100 };
5. Importantly, attachment the SVG to a div in the html file intended to hold the plot
svg = d3.select("#linegraph")
.append("svg")
.attr("width", margin.left + margin.right)
.attr("height", margin.top + margin.bottom)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
6. The creation of graph objects for each axis, the line representing the data, and the graph title
// Append groups for axes, line, and title
svg.append("g").attr("id", "x-axis");
svg.append("g").attr("id", "y-axis");
svg.append("path")
.attr("id", "line-path")
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 5);
svg.append("text")
.attr("id", "title")
.attr("text-anchor", "middle")
.style("font-size", "36px");
7. A function to update the plot when a different variable is selected. Note that this should be between the globals and the import of the csv.
// Global variables for D3 chart
let dataGlobal;
let svg;
let valueKey;
function updateChart() {
const container = document.getElementById('linegraph');
const rect = container.getBoundingClientRect();
const margin = { top: 60, right: 30, bottom: 60, left: 100 },
width = (rect.width - margin.left - margin.right)*0.95,
height = (rect.height - margin.top - margin.bottom)*.9;
// Update the SVG element dimensions
d3.select("#linegraph > svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
// Define scales (using x_var for x and the currently selected variable for y)
const x = d3.scaleTime()
.domain(d3.extent(dataGlobal, d => d[x_var]))
.range([0, width]);
const y = d3.scaleLinear()
.domain([d3.min(dataGlobal, d => d[valueKey]), d3.max(dataGlobal, d => d[valueKey])])
.range([height, 0]);
// Update axes
d3.select("#x-axis")
.attr("transform", `translate(0, ${height})`)
.call(d3.axisBottom(x))
.style("font-size", "24px")
// Rotate the x-axis labels 90 degrees
.selectAll("text")
.attr("transform", "rotate(90)")
.attr("dx", "0.5em")
.attr("dy", "-0.5em")
.style("text-anchor", "start");
d3.select("#y-axis")
.call(d3.axisLeft(y))
.style("font-size", "24px");
// Update the line path
d3.select("#line-path")
.attr("d", d3.line()
.x(d => x(d[x_var]))
.y(d => y(d[valueKey]))
(dataGlobal));
// Update the chart title
d3.select("#title")
.attr("x", (width / 2))
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "36px")
.text(valueKey)
.attr("transform", `translate(0, 0)`);
}
8. A traditional listener that updates the plot when the window dimension is changed. (This is not technically necessary, but the plot is unimpressive when this is not included.)
// Render the chart initially and update on window resize
updateChart();
window.addEventListener("resize", updateChart);
Complete Script
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>D3 Visualization with Code Display and Draggable Divider</title>
<!-- Prism CSS for syntax highlighting -->
<script src="https://d3js.org/d3.v6.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.28.0/themes/prism.min.css" rel="stylesheet" />
<style>
html, body {
margin: 0;
min-height: 100vh;
overflow-y: auto;
}
/* Dropdown stays fixed at the top left */
#variable-select {
position: fixed;
top: 10px;
left: 10px;
z-index: 10;
font-size: 24px;
margin-bottom:20px;
}
/* Chart area takes full width and grows with content */
/* Ensure the SVG fills its container */
#linegraph svg {
width: 100%;
height: 100vh;
}
</style>
</head>
<body>
<!-- Dropdown for selecting variable -->
<div style="padding-bottom:20px;"><select id="variable-select"></select></div>
<br><br>
<div id="linegraph"></div>
<script>
function D3LineWithDropdown(filename, x_var) {
// Global variables for D3 chart
let dataGlobal;
let svg;
let valueKey;
// updateChart uses the dimensions of the #linegraph container
function updateChart() {
const container = document.getElementById('linegraph');
const rect = container.getBoundingClientRect();
const margin = { top: 60, right: 30, bottom: 60, left: 100 },
width = (rect.width - margin.left - margin.right)*0.95,
height = (rect.height - margin.top - margin.bottom)*.9;
// Update the SVG element dimensions
d3.select("#linegraph > svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
// Define scales (using x_var for x and the currently selected variable for y)
const x = d3.scaleTime()
.domain(d3.extent(dataGlobal, d => d[x_var]))
.range([0, width]);
const y = d3.scaleLinear()
.domain([d3.min(dataGlobal, d => d[valueKey]), d3.max(dataGlobal, d => d[valueKey])])
.range([height, 0]);
// Update axes
d3.select("#x-axis")
.attr("transform", `translate(0, ${height})`)
.call(d3.axisBottom(x))
.style("font-size", "24px")
// Rotate the x-axis labels 90 degrees
.selectAll("text")
.attr("transform", "rotate(90)")
.attr("dx", "0.5em")
.attr("dy", "-0.5em")
.style("text-anchor", "start");
d3.select("#y-axis")
.call(d3.axisLeft(y))
.style("font-size", "24px");
// Update the line path
d3.select("#line-path")
.attr("d", d3.line()
.x(d => x(d[x_var]))
.y(d => y(d[valueKey]))
(dataGlobal));
// Update the chart title
d3.select("#title")
.attr("x", (width / 2))
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "36px")
.text(valueKey)
.attr("transform", `translate(0, 0)`);
}
// Load CSV and initialize chart
d3.csv(filename, function(d) {
// Use computed property name so that the key remains x_var (e.g., "DATE")
const parsedDate = d3.timeParse("%Y-%m-%d")(d[x_var]);
let row = { [x_var]: parsedDate };
for (let key in d) {
if (key !== x_var) {
row[key] = +d[key];
}
}
return row;
}).then(function(data) {
dataGlobal = data;
const keys = Object.keys(data[0]).filter(k => k !== x_var);
if (keys.length === 0) {
console.error("No numeric variables found in CSV.");
return;
}
valueKey = keys[0];
// Populate the dropdown menu
d3.select("#variable-select")
.selectAll("option")
.data(keys)
.enter()
.append("option")
.attr("value", d => d)
.text(d => d);
d3.select("#variable-select")
.on("change", function() {
valueKey = this.value;
updateChart();
});
// Create the SVG container inside #linegraph
const margin = { top: 60, right: 30, bottom: 30, left: 100 };
svg = d3.select("#linegraph")
.append("svg")
.attr("width", margin.left + margin.right)
.attr("height", margin.top + margin.bottom)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
// Append groups for axes, line, and title
svg.append("g").attr("id", "x-axis");
svg.append("g").attr("id", "y-axis");
svg.append("path")
.attr("id", "line-path")
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 5);
svg.append("text")
.attr("id", "title")
.attr("text-anchor", "middle")
.style("font-size", "36px");
// Render the chart initially and update on window resize
updateChart();
window.addEventListener("resize", updateChart);
});
}
let filename = "AllData.csv";
let x_var = "DATE";
D3LineWithDropdown(filename, x_var);
</script>
</body>
</html>